package model.algorithms.splitting;

import java.util.ArrayList;
import java.util.List;
import model.components.links.link.VirtualLink;
import model.components.networks.network.VirtualNetwork;
import model.components.networks.network.PhysicalNetwork;
import model.components.nodes.node.Node;
import model.components.nodes.node.VirtualNode;
import model.components.nodes.node.PhysicalNode;

public class SplittingAlgorithm
{
	private String name;
	public String getName(){ return name; }
	public void setName( String name ){ this.name = name; }
	
	private VirtualNetwork virtualNetwork;
	public VirtualNetwork getVirtualNetwork(){ return virtualNetwork; }
	public void setVirtualNetwork( VirtualNetwork virtualNetwork ){ this.virtualNetwork = virtualNetwork; }
	
	private List< PhysicalNetwork > physicalNetworks;
	public List< PhysicalNetwork > getPhysicalNetworks(){ return physicalNetworks; }	
	public void setPhysicalNetworks( List< PhysicalNetwork > physicalNetworks ){ this.physicalNetworks = physicalNetworks; }
	
	private int matchingNodesNumber[][];
	public int[][] getMatchingNodesNumber(){ return matchingNodesNumber; }
	public void setMatchingNodesNumber( int matchingNodesNumber[][] ){ this.matchingNodesNumber = matchingNodesNumber; }
	private int[][] matchingNodesNumber()
	{
		/** attributesMatchingNumber[ physicalNetworkNumber ][ virtualNodeNumber ] */
		int attributesMatchingNumber[][] = new int[ this.getPhysicalNetworks().size() ][ this.getVirtualNetwork().getNodes().size() ];
		int i = 0;
		
		for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )
		{
			for( VirtualNode virtualNode: this.getVirtualNetwork().getVirtualNodes() )
				for( PhysicalNode physicalNode: physicalNetwork.getPhysicalNodes() )
					if( physicalNode.getAvailableVirtualNodesNumber() > 0 &&
						virtualNode.getType().equals( physicalNode.getType() ) && 
						virtualNode.getOperatingSystem().equals( physicalNode.getOperatingSystem() ) && 
						virtualNode.getVirtualEnvironment().equals( physicalNode.getVirtualEnvironment() ) && 
						virtualNode.getCpu() < physicalNode.getAvailableCpu() && 
						virtualNode.getMemory() < physicalNode.getAvailableMemory() )
					{
						if( virtualNode.getType().equals( Node.ROUTER_TYPE ) || virtualNode.getType().equals( Node.SWITCH_TYPE ) )
							attributesMatchingNumber[ i ][ virtualNode.getNumber() ] += 1;
						
						else if( virtualNode.getType().equals( Node.SERVER_TYPE ) && virtualNode.getDiskSpace() < physicalNode.getAvailableDiskSpace() )
							attributesMatchingNumber[ i ][ virtualNode.getNumber() ] += 1;
					}
			
			i++;
		}
		
		return attributesMatchingNumber;
	}
	
	private boolean matchingNodes[][];
	public boolean[][] getMatchingNodes(){ return matchingNodes; }
	public void setMatchingNodes( boolean matchingNodes[][] ){ this.matchingNodes = matchingNodes; }
	private boolean[][] matchingNodes()
	{
		/** matchingNodes[ physicalNetworkNumber ][ virtualNodeNumber ] */
		boolean matchingNodes[][] = new boolean[ this.getPhysicalNetworks().size() ][ this.getVirtualNetwork().getNodes().size() ];
		int i = 0;
		
		for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )
		{
			for( VirtualNode virtualNode: this.getVirtualNetwork().getVirtualNodes() )
				for( PhysicalNode physicalNode: physicalNetwork.getPhysicalNodes() )
					if( physicalNode.getAvailableVirtualNodesNumber() > 0 &&
						virtualNode.getType().equals( physicalNode.getType() ) && 
						virtualNode.getOperatingSystem().equals( physicalNode.getOperatingSystem() ) && 
						virtualNode.getVirtualEnvironment().equals( physicalNode.getVirtualEnvironment() ) && 
						virtualNode.getCpu() < physicalNode.getAvailableCpu() && 
						virtualNode.getMemory() < physicalNode.getAvailableMemory() )
					{
						if( virtualNode.getType().equals( Node.ROUTER_TYPE ) || virtualNode.getType().equals( Node.SWITCH_TYPE ) )
						{
							matchingNodes[ i ][ virtualNode.getNumber() ] = true;
							break;
						}
						else if( virtualNode.getType().equals( Node.SERVER_TYPE ) && virtualNode.getDiskSpace() < physicalNode.getAvailableDiskSpace() )
						{
							matchingNodes[ i ][ virtualNode.getNumber() ] = true;
							break;
						}
					}
			
			i++;
		}
		
		return matchingNodes;
	}
	
	private double nodesMappingCost[][];
	public double[][] getNodesMappingCost(){ return nodesMappingCost; };
	public void setNodesMappingCost( double nodesMappingCost[][] ){ this.nodesMappingCost = nodesMappingCost; };
	private double[][] nodesMappingCost()
	{
		/** matchingNodes[ physicalNetworkNumber ][ virtualNodeNumber ] */
		boolean matchingNodes[][] = this.getMatchingNodes();
		double nodesMappingCost[][] = new double[ this.getPhysicalNetworks().size() ][ this.getVirtualNetwork().getNodes().size() ];
		
		for( int i = 0; i < this.getPhysicalNetworks().size(); i++ )
		{
			PhysicalNetwork physicalNetwork = this.getPhysicalNetworks().get( i );
			
			for( VirtualNode virtualNode: this.getVirtualNetwork().getVirtualNodes() )
			{
				if( matchingNodes[ i ][ virtualNode.getNumber() ] )
				{
					nodesMappingCost[ i ][ virtualNode.getNumber() ] = virtualNode.getCpu() * physicalNetwork.getCpuUnitTimeUnitPrice() + virtualNode.getMemory() * physicalNetwork.getMemoryUnitTimeUnitPrice();
					
					if( virtualNode.getType().equals( Node.SERVER_TYPE ) )
						nodesMappingCost[ i ][ virtualNode.getNumber() ] += virtualNode.getDiskSpace() * physicalNetwork.getDiskSpaceUnitTimeUnitPrice();
				}
				else
					nodesMappingCost[ i ][ virtualNode.getNumber() ] = Double.MAX_VALUE;
			}
		}
		
		return nodesMappingCost;
	}
	
	private double linksMappingCost[][][][];
	public double[][][][] getLinksMappingCost(){ return linksMappingCost; }
	public void setLinksMappingCost( double[][][][] linksMappingCost ){ this.linksMappingCost = linksMappingCost; }
	private double[][][][] linksMappingCost()
	{
		/** linksMappingCost[ physicalNetworkNumber ][ physicalNetworkNumber ][ virtualNodeNumber ][ virtualNodeNumber ] */ 
		int physicalNetworksNumber = this.getPhysicalNetworks().size();
		int virtualNodesNumber = this.getVirtualNetwork().getNodes().size();
		
		double linksMappingCost[][][][] = new double[ physicalNetworksNumber ][ physicalNetworksNumber ][ virtualNodesNumber ][ virtualNodesNumber ];
		
		for( int i = 0; i < physicalNetworksNumber; i++ )
			for( int j = 0; j < physicalNetworksNumber; j++ )
				for( VirtualLink virtualLink: this.getVirtualNetwork().getVirtualLinks() )
				{
					int k = virtualLink.getFirstNode().getNumber();
					int l = virtualLink.getSecondNode().getNumber();
					
					if( virtualLink.getBandwidth() != 0 )
					{
						if( i == j )
						{
							linksMappingCost[ i ][ j ][ k ][ l ] = virtualLink.getBandwidth() * this.getPhysicalNetworks().get( i ).getBandwidthUnitTimeUnitPrice();
							linksMappingCost[ i ][ j ][ l ][ k ] = virtualLink.getBandwidth() * this.getPhysicalNetworks().get( i ).getBandwidthUnitTimeUnitPrice();
						}
						else
						{
							linksMappingCost[ i ][ j ][ k ][ l ] = virtualLink.getBandwidth() * 3 * this.maxBandwidthUnitPrice();
							linksMappingCost[ i ][ j ][ l ][ k ] = virtualLink.getBandwidth() * 3 * this.maxBandwidthUnitPrice();
						}
					}
					else
						linksMappingCost[ i ][ j ][ k ][ l ] = Integer.MAX_VALUE;
				}
		
		return linksMappingCost;
	}
	private int maxBandwidthUnitPrice()
	{
		int maxBandwidthUnitPrice = 0;
		
		for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )
			if( physicalNetwork.getBandwidthUnitTimeUnitPrice() > maxBandwidthUnitPrice )
				maxBandwidthUnitPrice = physicalNetwork.getBandwidthUnitTimeUnitPrice();
		
		return maxBandwidthUnitPrice;
	}
	
	public SplittingAlgorithm( String name )
	{
		this.setName( name );
	}
	
	public List< VirtualNetwork > run( VirtualNetwork virtualNetwork )
 	{
		this.setVirtualNetwork( virtualNetwork );
		this.setMatchingNodesNumber( this.matchingNodesNumber() );
 		this.setMatchingNodes( this.matchingNodes() );
 		this.setNodesMappingCost( this.nodesMappingCost() );//
 		this.setLinksMappingCost( this.linksMappingCost() );//
 		
 		int bestNodesMapping[] = this.bestNodesMapping();
		
 		
 		double coste = this.networkMappingCost( bestNodesMapping );
 		
		if( coste < 2000000 )
		{
			bestNodesMapping = this.nodesMappingCheck( bestNodesMapping );
			
			//////
			
			//////
			
			if( bestNodesMapping != null )
				return this.virtualSubnetworks( bestNodesMapping );
		}
		
		return null;
	}
	protected int[] bestNodesMapping()
	{
		/** nodesMapping[ requestNodeNumber ] = substrateNetworkNumber; */
		
		return null;
	}
	protected int[] nodesMapping( int nodesMapping[], int trials )
	{
		/** nodesMapping[ requestNodeNumber ] = substrateNetworkNumber; */
		
		VirtualNetwork virtualNetwork = this.getVirtualNetwork();
		double linksMappingCost[][][][] = this.getLinksMappingCost();
		double nodesMappingCost[][] = this.getNodesMappingCost();
		double bandwidths[][] = virtualNetwork.getBandwidths();
		int virtualNodesNumber = virtualNetwork.getNodes().size();
		int physicalNetworksNumber = this.getPhysicalNetworks().size();
		
		for( int h = 0; h < trials; h++ )
		{
			int virtualNodeNumber = (int) ( Math.random() * virtualNodesNumber );
			int physicalNetworkNumber = (int) ( Math.random() * physicalNetworksNumber );
			double networkMappingCost = 0;
			
			for( int i = 0; i < virtualNodesNumber; i++ )
			{
				if( i == virtualNodeNumber )
					networkMappingCost += nodesMappingCost[ physicalNetworkNumber ][ i ];
				else
					networkMappingCost += nodesMappingCost[ nodesMapping[ i ] ][ i ];	
			}
				
			for( int i = 0; i < virtualNodesNumber; i++ )
				for( int j = i; j < virtualNodesNumber; j++ )
					if( bandwidths[ i ][ j ] != 0 )
					{
						if( i == virtualNodeNumber )
							networkMappingCost += linksMappingCost[ physicalNetworkNumber ][ nodesMapping[ j ] ][ i ][ j ];
						
						else if( j == virtualNodeNumber )
							networkMappingCost += linksMappingCost[ nodesMapping[ i ] ][ physicalNetworkNumber ][ i ][ j ];
						
						else if( i != virtualNodeNumber && j != virtualNodeNumber )
							networkMappingCost += linksMappingCost[ nodesMapping[ i ] ][ nodesMapping[ j ] ][ i ][ j ];
					}
			
			double currentNetworkMappingCost = this.networkMappingCost( nodesMapping );
			
			if( networkMappingCost < currentNetworkMappingCost )
				nodesMapping[ virtualNodeNumber ] = physicalNetworkNumber;
		}
		
		return nodesMapping;
	}
	protected double networkMappingCost( int nodesMapping[] )
	{
		int virtualNodesNumber = this.getVirtualNetwork().getNodes().size();
		double virtualNetworkBandwidths[][] = this.getVirtualNetwork().getBandwidths();
		double nodesMappingCost[][] = this.getNodesMappingCost();
		double linksMappingCost[][][][] = this.getLinksMappingCost();
		double networkMappingCost = 0;
		
		for( int i = 0; i < virtualNodesNumber; i++ )
			networkMappingCost += nodesMappingCost[ nodesMapping[ i ] ][ i ];
		
		for( int i = 0; i < virtualNodesNumber; i++ )
			for( int j = i; j < virtualNodesNumber; j++ )
				if( virtualNetworkBandwidths[ i ][ j ] != 0 )
					networkMappingCost += linksMappingCost[ nodesMapping[ i ] ][ nodesMapping[ j ] ][ i ][ j ];
		
		return networkMappingCost;
	}
	
	private int[] nodesMappingCheck( int nodesMapping[] )
	{
		double nodesMappingCost[][] = this.getNodesMappingCost();
		boolean nodesMappingCheck = false;
		
		int attributesMatchingNumber[][] = this.getMatchingNodesNumber();
		List< List< Integer > > sameRequestNodes = this.sameVirtualNodes();
				
		while( nodesMappingCheck == false )
		{
			nodesMappingCheck = true;
			
			for( int i = 0; i < this.getVirtualNetwork().getNodes().size(); i++ )
				for( int j = 0; j < this.getPhysicalNetworks().size(); j++ )
				{
					int counter = 0;
					
					for( int k = 0; k < sameRequestNodes.get( i ).size(); k++ )
					{
						if( nodesMapping[ sameRequestNodes.get( i ).get( k ) ] == j )
							counter++;
						
						// If the number of identical virtual nodes embedded in the same InP > number of matches,
						// make the cost of the extra nodes infinity
						
						if( counter > attributesMatchingNumber[ j ][ i ] )
						{
							nodesMappingCost[ j ][ sameRequestNodes.get( i ).get( k ) ] = Integer.MAX_VALUE;
							nodesMappingCheck = false;
						}
					}
				}
			
			this.setNodesMappingCost( nodesMappingCost );
			
			// If there is a wrong splitting, re-do the splitting with the new costs
			if( nodesMappingCheck == false )
				nodesMapping = this.bestNodesMapping();
			
			// Compute the new cost
			double networkMappingCost = this.networkMappingCost( nodesMapping );
			
			// If the new cost is infinity embedding is not possible return null 
			if( networkMappingCost > 20000 )
			{
				nodesMappingCheck = true;
				nodesMapping = null;
			}
		}
		
		return nodesMapping;
	}
	private List< List< Integer > > sameVirtualNodes()
	{
		VirtualNetwork virtualNetwork = this.getVirtualNetwork();
		List< List< Integer > > sameVirtualNodes = new ArrayList< List< Integer > >();
		
		for( int i = 0; i < virtualNetwork.getNodes().size(); i++ )
			sameVirtualNodes.add( new ArrayList< Integer >() );
		
		for( VirtualNode virtualNodeA: virtualNetwork.getVirtualNodes() )
			for( VirtualNode virtualNodeB: virtualNetwork.getVirtualNodes() )
				if( virtualNodeA.getType().equals( virtualNodeB.getType() ) && virtualNodeA.getOperatingSystem().equals( virtualNodeB.getOperatingSystem() ) && virtualNodeA.getVirtualEnvironment().equals( virtualNodeB.getVirtualEnvironment() ) )
					sameVirtualNodes.get( virtualNodeA.getNumber() ).add( virtualNodeB.getNumber() );
		
		return sameVirtualNodes;
	}
	
	private List< VirtualNetwork > virtualSubnetworks( int nodesMapping[] )
	{
		VirtualNetwork virtualNetwork = this.getVirtualNetwork();
		List< PhysicalNetwork > physicalNetworks = this.getPhysicalNetworks();
		
		int virtualNodesNumber = virtualNetwork.getNodes().size();
		List< VirtualNetwork > virtualSubnetworks = new ArrayList< VirtualNetwork >();
		String virtualSubnetworksNames[] = new String[ virtualNodesNumber ];
		String physicalNetworksNamePrefix = physicalNetworks.get( 0 ).getNamePrefix();
		
		for( int i = 0; i < virtualNodesNumber; i++ )
			virtualSubnetworksNames[ i ] = physicalNetworksNamePrefix + nodesMapping[ i ];
			
		int i = 0;
		
		for( PhysicalNetwork physicalNetwork: physicalNetworks )
		{
			VirtualNetwork virtualSubnetwork = new VirtualNetwork( virtualNetwork.getName() + "part", i++, virtualNetwork.getStartingDate(), virtualNetwork.getEndingDate() );
			
			for( VirtualNode virtualNode: virtualNetwork.getVirtualNodes() )
				if( virtualSubnetworksNames[ virtualNode.getNumber() ].contains( physicalNetwork.getName() ) )
				{
					virtualSubnetwork.getGraph().addVertex( virtualNode );
					virtualSubnetwork.setPhysicalNetworkName( physicalNetwork.getName() );
				}
			
			virtualSubnetworks.add( virtualSubnetwork );
		}
		
		for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )
			for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
			{
				int checkingLink = 0;
				
				for( VirtualNode virtualNode: virtualSubnetwork.getVirtualNodes() )
				{
					if( virtualNode.getNumber() == virtualLink.getFirstNode().getNumber() )
						checkingLink++;
					
					if( virtualNode.getNumber() == virtualLink.getSecondNode().getNumber() )
						checkingLink++;
				}
				
				if( checkingLink == 2 )
					virtualSubnetwork.addLinkAndNodes( virtualLink, virtualLink.getFirstNode(), virtualLink.getSecondNode() );
			}
		
		for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )
			this.setNumberVirtualNetwork( virtualSubnetwork );
		
		return virtualSubnetworks;
	}
	private void setNumberVirtualNetwork( VirtualNetwork virtualNetwork )
	{
		int i = 0;
		
		for( VirtualNode virtualNode: virtualNetwork.getVirtualNodes() )
			virtualNode.setNumber( i++ );
		
		i = 0;
		
		for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
			virtualLink.setNumber( i++ );
	}
	
}
